/**
 Copyright (C) 2013 Yuyao Liang

 This source code is release under the BSD License.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.

 Created on Dec 19, 2013 10:20:33 AM
 */
package com.googlecode.backtest.rule.simulation;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.joda.time.LocalDate;

import com.google.srclib.data.DurationType;
import com.google.srclib.data.TAKeyName;
import com.googlecode.backtest.dataset.BackTestStockDataset;
import com.googlecode.backtest.dataset.IBackTestStockDataset;
import com.googlecode.backtest.dataset.manager.DataReadyEvent;
import com.googlecode.backtest.rule.IBackTestParameters;
import com.googlecode.backtest.rule.action.BackTestActionMethod;
import com.googlecode.backtest.rule.action.IBackTestAction;
import com.googlecode.backtest.rule.action.IBackTestActions;

/**
 * @author lianyuy
 * 
 */
public class BackTestValueSimulation extends
		BackTestSimulation<IBackTestStockDataset, IBackTestParameters> {
	private final String ID = getClass().getName();
	private final String VERSION = Messages
			.getString("BackTestValueSimulation.VERSION"); //$NON-NLS-1$
	private final String KEY = Messages
			.getString("BackTestValueSimulation.KEY"); //$NON-NLS-1$
	private IBackTestStockDataset result = null;
	private Map<String, Object> dataset = new HashMap<String, Object>();
	private IBackTestStockDataset stockdataset = null;
	private IBackTestActions actions = null;

	/**
	 * @param id
	 * @param data_ids
	 * @param parameter
	 */
	public BackTestValueSimulation(String id, List<String> data_ids,
			IBackTestParameters parameter) {
		super(id, data_ids, parameter);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.googlecode.backtest.rule.IAlgorithm#getKey()
	 */
	@Override
	public String getKey() {
		return KEY;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.googlecode.backtest.rule.IAlgorithm#getResult()
	 */
	@Override
	public IBackTestStockDataset getResult() {
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.srclib.base.IDable#getID()
	 */
	@Override
	public String getID() {
		return ID;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.google.srclib.base.Versionable#getVersion()
	 */
	@Override
	public String getVersion() {
		return VERSION;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.googlecode.backtest.dataset.manager.DataReadyEventListener#OnDataReady
	 * (com.googlecode.backtest.dataset.manager.DataReadyEvent)
	 */
	@Override
	public void OnDataReady(DataReadyEvent event) {
		String key = event.getKey();
		dataset.put(key, (Object) event.getManager().getData(key));

		if (calculate()) {
			System.out.println("Value simulation Data ready!"); //$NON-NLS-1$
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.googlecode.backtest.rule.simulation.IBackTestSimulation#calculate()
	 */
	@Override
	public boolean calculate() {
		if (!validToRun())
			return false;
		doCalculation();
		return true;
	}

	private boolean validToRun() {
		boolean result = true;
		for (String id : data_ids) {
			boolean contained = dataset.containsKey(id);
			result = result && contained;
			if (contained) {
				Object v = dataset.get(id);
				if (v instanceof IBackTestActions)
					actions = (IBackTestActions) v;
				if (v instanceof IBackTestStockDataset)
					stockdataset = (IBackTestStockDataset) v;
			}
		}
		return result;
	}

	private void doCalculation() {
		LocalDate[] dates = stockdataset.getDates();
		double[] highs = stockdataset.getArray(TAKeyName.High.getKey());
		double[] lows = stockdataset.getArray(TAKeyName.Low.getKey());
		double[] closes = stockdataset.getArray(TAKeyName.Close.getKey());
		double[] values = new double[dates.length];

		boolean invested = false;
		int lastindex = 0;
		LocalDate beginDate = dates[0];
		double beginInvestment = 100.0;
		double beginShares = 0.0;

		Iterator<IBackTestAction> iter = actions.getIterator();
		BackTestActionMethod method = BackTestActionMethod.Hold;
		while (iter.hasNext()) {
			IBackTestAction action = iter.next();
			LocalDate date = action.getDate();
			method = action.getMethod();
			if (!invested && method == BackTestActionMethod.Buy) {
				invested = true;
			}
			if (invested) {
				if (method == BackTestActionMethod.Buy) {
					while (beginDate.isBefore(date)) {
						values[lastindex] = beginInvestment;
						lastindex++;
						beginDate = dates[lastindex];
					}
					if (beginDate.isEqual(date)) {
						beginShares = beginInvestment / closes[lastindex];
						values[lastindex] = beginInvestment;
						lastindex++;
					}

				} else if (method == BackTestActionMethod.Hold) {
					// Do nothing

				} else if (method == BackTestActionMethod.Sell) {
					while (beginDate.isBefore(date)) {
						values[lastindex] = beginShares * closes[lastindex];
						lastindex++;
						beginDate = dates[lastindex];
					}
					if (beginDate.isEqual(date)) {
						beginInvestment = beginShares * closes[lastindex];
						values[lastindex] = beginInvestment;
						lastindex++;
					}
				}
			}
		}
		
		for (int index = lastindex; index < closes.length; index++) {
			if (method == BackTestActionMethod.Buy) {
				values[index] = beginInvestment;
			} else if (method == BackTestActionMethod.Sell) {
				values[index] = beginShares * closes[index];
			}
		}

		BackTestStockDataset data = new BackTestStockDataset(DurationType.DAYS);
		result = (IBackTestStockDataset) stockdataset.cloneTo(data);

		for (int i = 0; i < closes.length; i++) {
			result.put(KEY, values[i]);
		}
	}

}
